﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Mp3FileSystemManager.cs" company="GT">
//   Gunther Taborga
// </copyright>
// <summary>
//   Defines the Mp3FileSystemManager type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace BatchID3.Mp3
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;

    using BatchID3.Enumerations;
    using BatchID3.Exceptions;
    using BatchID3.Extensions;
    using BatchID3.FileSystem;

    public class Mp3FileSystemManager : IMp3FileSystemManager
    {
        public const string Mp3Extension = ".mp3";

        public const string Mp3ExtensionFilter = "*.mp3";

        public const string TempMacFilePrefix = "._";

        public Mp3FileSystemManager(IFileSystemManager fileSystem)
        {
            this.FileSystem = fileSystem;
        }

        public virtual char EscapeCharacter
        {
            get
            {
                return '_';
            }
        }

        public IFileSystemManager FileSystem { get; private set; }

        public void Delete(IMp3Entity mp3)
        {
            this.FileSystem.DeleteFile(mp3.FilePath);
        }

        public IEnumerable<string> GetMp3FilePaths(string directory, SearchOption searchOption)
        {
            if (!this.FileSystem.DirectoryExists(directory))
            {
                return Enumerable.Empty<string>();
            }

            return this.FileSystem.GetFiles(directory, Mp3ExtensionFilter, searchOption)
                                  .Where(mp3 => !this.FileSystem.GetFileName(mp3).StartsWith(TempMacFilePrefix));
        }

        public string GetNewDirectoryFromId3(string newDirectory, IMp3Entity mp3Entity, IEnumerable<TagToken> tagTokens)
        {
            var directoryParts = this.GetNewDirectoryParts(newDirectory, mp3Entity, tagTokens).ToList();
            directoryParts.Add(this.FileSystem.GetFileName(mp3Entity.FilePath));

            return this.FileSystem.Combine(directoryParts.ToArray());
        }

        public string GetNewFileNameFromId3(IMp3Entity mp3Entity, IEnumerable<TagToken> tagTokens, char delimiter)
        {
            var directory = this.FileSystem.GetDirectoryName(mp3Entity.FilePath);

            var fileParts = tagTokens.Select(t => this.EscapePath(mp3Entity.GetTagAttributeFromToken(t), this.EscapeCharacter))
                                     .Where(a => !string.IsNullOrEmpty(a)).ToList();

            return fileParts.Count == 0 ? mp3Entity.FilePath : this.GetNewFilePath(directory, fileParts, delimiter);
        }

        public string RenameById3(IMp3Entity mp3Entity, IEnumerable<TagToken> tagTokens, char delimiter)
        {
            var newFilePath = this.GetNewFileNameFromId3(mp3Entity, tagTokens, delimiter);

            if (!string.IsNullOrEmpty(newFilePath) && !this.FileSystem.FileExists(newFilePath))
            {
                this.FileSystem.MoveFile(mp3Entity.FilePath, newFilePath);
                return newFilePath;
            }

            return mp3Entity.FilePath;
        }

        public string SortById3(IMp3Entity mp3Entity, IEnumerable<TagToken> tagTokens, string destinationDirectory = "")
        {
            var newDirectory = this.CreateDirectoryFromId3(destinationDirectory, mp3Entity, tagTokens);

            var currentMp3Directory = this.FileSystem.GetDirectoryName(mp3Entity.FilePath);
            if (string.IsNullOrEmpty(currentMp3Directory) || currentMp3Directory.Equals(newDirectory))
            {
                return mp3Entity.FilePath;
            }

            var destinationPath = this.GetNewFilePath(newDirectory, mp3Entity);
            if (this.FileSystem.FileExists(destinationPath))
            {
                return mp3Entity.FilePath;
            }

            this.FileSystem.MoveFile(mp3Entity.FilePath, destinationPath);
            if (!this.FileSystem.GetFiles(currentMp3Directory, "*.*", SearchOption.AllDirectories).Any())
            {
                this.FileSystem.DeleteDirectory(currentMp3Directory, true);
            }

            return destinationPath;
        }

        public void SetTagValue(IMp3Entity mp3Entity, TagToken tagToken, string prefix, string value, string suffix)
        {
            var mp3Property = GetMp3Property(mp3Entity, tagToken);

            if (mp3Property == null)
            {
                throw new BatchId3Exception("No IMp3Entity PropertyInfo was found with Name matching any TagToken's StringValue attribute.");
            }

            if (mp3Property.PropertyType == typeof(uint))
            {
                mp3Property.SetValue(mp3Entity, Convert.ToUInt32(value), null);
            }
            else
            {
                mp3Property.SetValue(mp3Entity, string.Format("{0}{1}{2}", prefix, value, suffix), null);
            }
        }

        private static PropertyInfo GetMp3Property(IMp3Entity mp3Entity, TagToken tagToken)
        {
            return mp3Entity.GetType().GetPublicProperties().FirstOrDefault(p => tagToken.GetAllStringValues().Contains(p.Name));
        }

        private static string GetNewFileName(char delimiter, IEnumerable<string> fileParts)
        {
            var fileName = string.Join(string.Format(" {0} ", delimiter), fileParts);
            return string.Format("{0}{1}", fileName, Mp3Extension); 
        }

        private string CreateDirectoryFromId3(string directory, IMp3Entity mp3, IEnumerable<TagToken> tagTokens)
        {
            var newDirectory = this.GetNewDirectory(directory, mp3, tagTokens);
            this.FileSystem.CreateDirectory(newDirectory);

            return newDirectory;
        }

        private bool DoesDirectoryEndWithPathSeparator(string destinationPath)
        {
            return destinationPath.EndsWith(this.FileSystem.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture));
        }

        private string EscapePath(string path, char escapeCharacter)
        {
            if (string.IsNullOrEmpty(path))
            {
                return string.Empty;
            }

            var invalidPathChars = this.FileSystem.GetInvalidPathChars();
            var invalidFileNameChars = this.FileSystem.GetInvalidFileNameChars();

            if (invalidPathChars.Contains(escapeCharacter) || invalidFileNameChars.Contains(escapeCharacter))
            {
                throw new InvalidOperationException(string.Format("'{0}' is an invalid path/file name character.", escapeCharacter));
            }

            var pathChars = path.ToCharArray();
            for (var i = 0; i < pathChars.Length; i++)
            {
                var pathChar = pathChars[i];
                if (invalidPathChars.Contains(pathChar) || invalidFileNameChars.Contains(pathChar))
                {
                    pathChars[i] = escapeCharacter;
                }
            }

            return new string(pathChars);
        }

        private string GetNewDirectory(string directory, IMp3Entity mp3, IEnumerable<TagToken> tagTokens)
        {
            var directoryParts = this.GetNewDirectoryParts(directory, mp3, tagTokens);
            return this.GetNewDirectory(directoryParts);
        }

        private string GetNewDirectory(IEnumerable<string> directoryParts)
        {
            return this.FileSystem.GetActualPath(this.FileSystem.Combine(directoryParts.ToArray()));
        }

        private IEnumerable<string> GetNewDirectoryParts(string directory, IMp3Entity mp3, IEnumerable<TagToken> tagTokens)
        {
            var newDirectory = string.IsNullOrEmpty(directory) ? this.FileSystem.GetDirectoryName(mp3.FilePath) : directory;
            var directoryParts = new List<string> { newDirectory };
            var tokens = tagTokens.ToList();

            directoryParts.AddRange(tokens.Select(tagToken => this.EscapePath(mp3.GetTagAttributeFromToken(tagToken), this.EscapeCharacter))
                                          .Where(directoryPart => !string.IsNullOrEmpty(directoryPart)));

            return directoryParts;
        }

        private string GetNewFilePath(string directory, IEnumerable<string> fileParts, char delimiter)
        {
            return this.FileSystem.Combine(directory, GetNewFileName(delimiter, fileParts));
        }

        private string GetNewFilePath(string newDirectory, IMp3Entity mp3)
        {
            return this.FileSystem.Combine(newDirectory, this.FileSystem.GetFileName(mp3.FilePath));
        }
    }
}